// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/cdm_adapter.h"

#include <stddef.h>
#include <utility>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/cdm_initialized_promise.h"
#include "media/base/cdm_key_information.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/limits.h"
#include "media/base/sample_format.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/cdm/cdm_allocator.h"
#include "media/cdm/cdm_file_io.h"
#include "media/cdm/cdm_helpers.h"
#include "media/cdm/cdm_wrapper.h"
#include "ui/gfx/geometry/rect.h"

namespace media {

namespace {

    cdm::SessionType ToCdmSessionType(CdmSessionType session_type)
    {
        switch (session_type) {
        case CdmSessionType::TEMPORARY_SESSION:
            return cdm::kTemporary;
        case CdmSessionType::PERSISTENT_LICENSE_SESSION:
            return cdm::kPersistentLicense;
        case CdmSessionType::PERSISTENT_RELEASE_MESSAGE_SESSION:
            return cdm::kPersistentKeyRelease;
        }

        NOTREACHED() << "Unexpected session type: " << static_cast<int>(session_type);
        return cdm::kTemporary;
    }

    cdm::InitDataType ToCdmInitDataType(EmeInitDataType init_data_type)
    {
        switch (init_data_type) {
        case EmeInitDataType::CENC:
            return cdm::kCenc;
        case EmeInitDataType::KEYIDS:
            return cdm::kKeyIds;
        case EmeInitDataType::WEBM:
            return cdm::kWebM;
        case EmeInitDataType::UNKNOWN:
            break;
        }

        NOTREACHED();
        return cdm::kKeyIds;
    }

    CdmPromise::Exception ToMediaExceptionType(cdm::Error error)
    {
        switch (error) {
        case cdm::kNotSupportedError:
            return CdmPromise::NOT_SUPPORTED_ERROR;
        case cdm::kInvalidStateError:
            return CdmPromise::INVALID_STATE_ERROR;
        case cdm::kInvalidAccessError:
            return CdmPromise::INVALID_ACCESS_ERROR;
        case cdm::kQuotaExceededError:
            return CdmPromise::QUOTA_EXCEEDED_ERROR;
        case cdm::kUnknownError:
            return CdmPromise::UNKNOWN_ERROR;
        case cdm::kClientError:
            return CdmPromise::CLIENT_ERROR;
        case cdm::kOutputError:
            return CdmPromise::OUTPUT_ERROR;
        }

        NOTREACHED() << "Unexpected cdm::Error " << error;
        return CdmPromise::UNKNOWN_ERROR;
    }

    ContentDecryptionModule::MessageType ToMediaMessageType(
        cdm::MessageType message_type)
    {
        switch (message_type) {
        case cdm::kLicenseRequest:
            return ContentDecryptionModule::LICENSE_REQUEST;
        case cdm::kLicenseRenewal:
            return ContentDecryptionModule::LICENSE_RENEWAL;
        case cdm::kLicenseRelease:
            return ContentDecryptionModule::LICENSE_RELEASE;
        }

        NOTREACHED() << "Unexpected cdm::MessageType " << message_type;
        return ContentDecryptionModule::LICENSE_REQUEST;
    }

    CdmKeyInformation::KeyStatus ToCdmKeyInformationKeyStatus(
        cdm::KeyStatus status)
    {
        switch (status) {
        case cdm::kUsable:
            return CdmKeyInformation::USABLE;
        case cdm::kInternalError:
            return CdmKeyInformation::INTERNAL_ERROR;
        case cdm::kExpired:
            return CdmKeyInformation::EXPIRED;
        case cdm::kOutputRestricted:
            return CdmKeyInformation::OUTPUT_RESTRICTED;
        case cdm::kOutputDownscaled:
            return CdmKeyInformation::OUTPUT_DOWNSCALED;
        case cdm::kStatusPending:
            return CdmKeyInformation::KEY_STATUS_PENDING;
        case cdm::kReleased:
            return CdmKeyInformation::RELEASED;
        }

        NOTREACHED() << "Unexpected cdm::KeyStatus " << status;
        return CdmKeyInformation::INTERNAL_ERROR;
    }

    cdm::AudioDecoderConfig::AudioCodec ToCdmAudioCodec(AudioCodec codec)
    {
        switch (codec) {
        case kCodecVorbis:
            return cdm::AudioDecoderConfig::kCodecVorbis;
        case kCodecAAC:
            return cdm::AudioDecoderConfig::kCodecAac;
        default:
            DVLOG(1) << "Unsupported AudioCodec " << codec;
            return cdm::AudioDecoderConfig::kUnknownAudioCodec;
        }
    }

    cdm::VideoDecoderConfig::VideoCodec ToCdmVideoCodec(VideoCodec codec)
    {
        switch (codec) {
        case kCodecVP8:
            return cdm::VideoDecoderConfig::kCodecVp8;
        case kCodecH264:
            return cdm::VideoDecoderConfig::kCodecH264;
        case kCodecVP9:
            return cdm::VideoDecoderConfig::kCodecVp9;
        default:
            DVLOG(1) << "Unsupported VideoCodec " << codec;
            return cdm::VideoDecoderConfig::kUnknownVideoCodec;
        }
    }

    cdm::VideoDecoderConfig::VideoCodecProfile ToCdmVideoCodecProfile(
        VideoCodecProfile profile)
    {
        switch (profile) {
        case VP8PROFILE_ANY:
        // TODO(servolk): See crbug.com/592074. We'll need to update this code to
        // handle different VP9 profiles properly after adding VP9 profiles in
        // media/cdm/api/content_decryption_module.h in a separate CL.
        // For now return kProfileNotNeeded to avoid breaking unit tests.
        case VP9PROFILE_PROFILE0:
        case VP9PROFILE_PROFILE1:
        case VP9PROFILE_PROFILE2:
        case VP9PROFILE_PROFILE3:
            return cdm::VideoDecoderConfig::kProfileNotNeeded;
        case H264PROFILE_BASELINE:
            return cdm::VideoDecoderConfig::kH264ProfileBaseline;
        case H264PROFILE_MAIN:
            return cdm::VideoDecoderConfig::kH264ProfileMain;
        case H264PROFILE_EXTENDED:
            return cdm::VideoDecoderConfig::kH264ProfileExtended;
        case H264PROFILE_HIGH:
            return cdm::VideoDecoderConfig::kH264ProfileHigh;
        case H264PROFILE_HIGH10PROFILE:
            return cdm::VideoDecoderConfig::kH264ProfileHigh10;
        case H264PROFILE_HIGH422PROFILE:
            return cdm::VideoDecoderConfig::kH264ProfileHigh422;
        case H264PROFILE_HIGH444PREDICTIVEPROFILE:
            return cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive;
        default:
            DVLOG(1) << "Unsupported VideoCodecProfile " << profile;
            return cdm::VideoDecoderConfig::kUnknownVideoCodecProfile;
        }
    }

    cdm::VideoFormat ToCdmVideoFormat(VideoPixelFormat format)
    {
        switch (format) {
        case PIXEL_FORMAT_YV12:
            return cdm::kYv12;
        case PIXEL_FORMAT_I420:
            return cdm::kI420;
        default:
            DVLOG(1) << "Unsupported VideoPixelFormat " << format;
            return cdm::kUnknownVideoFormat;
        }
    }

    cdm::StreamType ToCdmStreamType(Decryptor::StreamType stream_type)
    {
        switch (stream_type) {
        case Decryptor::kAudio:
            return cdm::kStreamTypeAudio;
        case Decryptor::kVideo:
            return cdm::kStreamTypeVideo;
        }

        NOTREACHED() << "Unexpected Decryptor::StreamType " << stream_type;
        return cdm::kStreamTypeVideo;
    }

    Decryptor::Status ToMediaDecryptorStatus(cdm::Status status)
    {
        switch (status) {
        case cdm::kSuccess:
            return Decryptor::kSuccess;
        case cdm::kNoKey:
            return Decryptor::kNoKey;
        case cdm::kNeedMoreData:
            return Decryptor::kNeedMoreData;
        case cdm::kDecryptError:
            return Decryptor::kError;
        case cdm::kDecodeError:
            return Decryptor::kError;
        case cdm::kSessionError:
        case cdm::kDeferredInitialization:
            break;
        }

        NOTREACHED() << "Unexpected cdm::Status " << status;
        return Decryptor::kError;
    }

    SampleFormat ToMediaSampleFormat(cdm::AudioFormat format)
    {
        switch (format) {
        case cdm::kAudioFormatU8:
            return kSampleFormatU8;
        case cdm::kAudioFormatS16:
            return kSampleFormatS16;
        case cdm::kAudioFormatS32:
            return kSampleFormatS32;
        case cdm::kAudioFormatF32:
            return kSampleFormatF32;
        case cdm::kAudioFormatPlanarS16:
            return kSampleFormatPlanarS16;
        case cdm::kAudioFormatPlanarF32:
            return kSampleFormatPlanarF32;
        case cdm::kUnknownAudioFormat:
            return kUnknownSampleFormat;
        }

        NOTREACHED() << "Unexpected cdm::AudioFormat " << format;
        return kUnknownSampleFormat;
    }

    // Fill |input_buffer| based on the values in |encrypted|. |subsamples|
    // is used to hold some of the data. |input_buffer| will contain pointers
    // to data contained in |encrypted| and |subsamples|, so the lifetime of
    // |input_buffer| must be <= the lifetime of |encrypted| and |subsamples|.
    void ToCdmInputBuffer(const scoped_refptr<DecoderBuffer>& encrypted_buffer,
        std::vector<cdm::SubsampleEntry>* subsamples,
        cdm::InputBuffer* input_buffer)
    {
        // End of stream buffers are represented as empty resources.
        DCHECK(!input_buffer->data);
        if (encrypted_buffer->end_of_stream())
            return;

        input_buffer->data = encrypted_buffer->data();
        input_buffer->data_size = encrypted_buffer->data_size();

        const DecryptConfig* decrypt_config = encrypted_buffer->decrypt_config();
        input_buffer->key_id = reinterpret_cast<const uint8_t*>(decrypt_config->key_id().data());
        input_buffer->key_id_size = decrypt_config->key_id().size();
        input_buffer->iv = reinterpret_cast<const uint8_t*>(decrypt_config->iv().data());
        input_buffer->iv_size = decrypt_config->iv().size();

        DCHECK(subsamples->empty());
        size_t num_subsamples = decrypt_config->subsamples().size();
        if (num_subsamples > 0) {
            subsamples->reserve(num_subsamples);
            for (const auto& sample : decrypt_config->subsamples()) {
                subsamples->push_back(
                    cdm::SubsampleEntry(sample.clear_bytes, sample.cypher_bytes));
            }
        }

        input_buffer->subsamples = subsamples->data();
        input_buffer->num_subsamples = num_subsamples;
        input_buffer->timestamp = encrypted_buffer->timestamp().InMicroseconds();
    }

    void* GetCdmHost(int host_interface_version, void* user_data)
    {
        if (!host_interface_version || !user_data)
            return nullptr;

        static_assert(
            cdm::ContentDecryptionModule::Host::kVersion == cdm::Host_8::kVersion,
            "update the code below");

        // Ensure IsSupportedCdmHostVersion matches implementation of this function.
        // Always update this DCHECK when updating this function.
        // If this check fails, update this function and DCHECK or update
        // IsSupportedCdmHostVersion.

        DCHECK(
            // Future version is not supported.
            !IsSupportedCdmHostVersion(cdm::Host_8::kVersion + 1) &&
            // Current version is supported.
            IsSupportedCdmHostVersion(cdm::Host_8::kVersion) &&
            // Include all previous supported versions (if any) here.
            // One older than the oldest supported version is not supported.
            !IsSupportedCdmHostVersion(cdm::Host_8::kVersion - 1));
        DCHECK(IsSupportedCdmHostVersion(host_interface_version));

        CdmAdapter* cdm_adapter = static_cast<CdmAdapter*>(user_data);
        DVLOG(1) << "Create CDM Host with version " << host_interface_version;
        switch (host_interface_version) {
        case cdm::Host_8::kVersion:
            return static_cast<cdm::Host_8*>(cdm_adapter);
        default:
            NOTREACHED() << "Unexpected host interface version "
                         << host_interface_version;
            return nullptr;
        }
    }

} // namespace

// static
void CdmAdapter::Create(
    const std::string& key_system,
    const base::FilePath& cdm_path,
    const CdmConfig& cdm_config,
    std::unique_ptr<CdmAllocator> allocator,
    const CreateCdmFileIOCB& create_cdm_file_io_cb,
    const SessionMessageCB& session_message_cb,
    const SessionClosedCB& session_closed_cb,
    const SessionKeysChangeCB& session_keys_change_cb,
    const SessionExpirationUpdateCB& session_expiration_update_cb,
    const CdmCreatedCB& cdm_created_cb)
{
    DCHECK(!key_system.empty());
    DCHECK(!session_message_cb.is_null());
    DCHECK(!session_closed_cb.is_null());
    DCHECK(!session_keys_change_cb.is_null());
    DCHECK(!session_expiration_update_cb.is_null());

    scoped_refptr<CdmAdapter> cdm = new CdmAdapter(
        key_system, cdm_config, std::move(allocator), create_cdm_file_io_cb,
        session_message_cb, session_closed_cb, session_keys_change_cb,
        session_expiration_update_cb);

    // |cdm| ownership passed to the promise.
    std::unique_ptr<CdmInitializedPromise> cdm_created_promise(
        new CdmInitializedPromise(cdm_created_cb, cdm));

    cdm->Initialize(cdm_path, std::move(cdm_created_promise));
}

CdmAdapter::CdmAdapter(
    const std::string& key_system,
    const CdmConfig& cdm_config,
    std::unique_ptr<CdmAllocator> allocator,
    const CreateCdmFileIOCB& create_cdm_file_io_cb,
    const SessionMessageCB& session_message_cb,
    const SessionClosedCB& session_closed_cb,
    const SessionKeysChangeCB& session_keys_change_cb,
    const SessionExpirationUpdateCB& session_expiration_update_cb)
    : key_system_(key_system)
    , cdm_config_(cdm_config)
    , session_message_cb_(session_message_cb)
    , session_closed_cb_(session_closed_cb)
    , session_keys_change_cb_(session_keys_change_cb)
    , session_expiration_update_cb_(session_expiration_update_cb)
    , audio_samples_per_second_(0)
    , audio_channel_layout_(CHANNEL_LAYOUT_NONE)
    , allocator_(std::move(allocator))
    , create_cdm_file_io_cb_(create_cdm_file_io_cb)
    , task_runner_(base::ThreadTaskRunnerHandle::Get())
    , weak_factory_(this)
{
    DCHECK(!key_system_.empty());
    DCHECK(!session_message_cb_.is_null());
    DCHECK(!session_closed_cb_.is_null());
    DCHECK(!session_keys_change_cb_.is_null());
    DCHECK(!session_expiration_update_cb_.is_null());
    DCHECK(allocator_);
}

CdmAdapter::~CdmAdapter() { }

CdmWrapper* CdmAdapter::CreateCdmInstance(const std::string& key_system,
    const base::FilePath& cdm_path)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // TODO(jrummell): We need to call INITIALIZE_CDM_MODULE() and
    // DeinitializeCdmModule(). However, that should only be done once for the
    // library.
    base::NativeLibraryLoadError error;
    library_.Reset(base::LoadNativeLibrary(cdm_path, &error));
    if (!library_.is_valid()) {
        DVLOG(1) << "CDM instance for " + key_system + " could not be created. "
                 << error.ToString();
        return nullptr;
    }

    CreateCdmFunc create_cdm_func = reinterpret_cast<CreateCdmFunc>(
        library_.GetFunctionPointer("CreateCdmInstance"));
    if (!create_cdm_func) {
        DVLOG(1) << "No CreateCdmInstance() in library for " + key_system;
        return nullptr;
    }

    CdmWrapper* cdm = CdmWrapper::Create(create_cdm_func, key_system.data(),
        key_system.size(), GetCdmHost, this);

    DVLOG(1) << "CDM instance for " + key_system + (cdm ? "" : " could not be") + " created.";
    return cdm;
}

void CdmAdapter::Initialize(const base::FilePath& cdm_path,
    std::unique_ptr<media::SimpleCdmPromise> promise)
{
    cdm_.reset(CreateCdmInstance(key_system_, cdm_path));
    if (!cdm_) {
        promise->reject(CdmPromise::INVALID_ACCESS_ERROR, 0,
            "Unable to create CDM.");
        return;
    }

    cdm_->Initialize(cdm_config_.allow_distinctive_identifier,
        cdm_config_.allow_persistent_state);
    promise->resolve();
}

void CdmAdapter::SetServerCertificate(
    const std::vector<uint8_t>& certificate,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    if (certificate.size() < limits::kMinCertificateLength || certificate.size() > limits::kMaxCertificateLength) {
        promise->reject(CdmPromise::INVALID_ACCESS_ERROR, 0,
            "Incorrect certificate.");
        return;
    }

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->SetServerCertificate(promise_id, certificate.data(),
        certificate.size());
}

void CdmAdapter::CreateSessionAndGenerateRequest(
    CdmSessionType session_type,
    EmeInitDataType init_data_type,
    const std::vector<uint8_t>& init_data,
    std::unique_ptr<NewSessionCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->CreateSessionAndGenerateRequest(
        promise_id, ToCdmSessionType(session_type),
        ToCdmInitDataType(init_data_type), init_data.data(), init_data.size());
}

void CdmAdapter::LoadSession(CdmSessionType session_type,
    const std::string& session_id,
    std::unique_ptr<NewSessionCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->LoadSession(promise_id, ToCdmSessionType(session_type),
        session_id.data(), session_id.size());
}

void CdmAdapter::UpdateSession(const std::string& session_id,
    const std::vector<uint8_t>& response,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(!session_id.empty());
    DCHECK(!response.empty());

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->UpdateSession(promise_id, session_id.data(), session_id.size(),
        response.data(), response.size());
}

void CdmAdapter::CloseSession(const std::string& session_id,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(!session_id.empty());

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->CloseSession(promise_id, session_id.data(), session_id.size());
}

void CdmAdapter::RemoveSession(const std::string& session_id,
    std::unique_ptr<SimpleCdmPromise> promise)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(!session_id.empty());

    uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
    cdm_->RemoveSession(promise_id, session_id.data(), session_id.size());
}

CdmContext* CdmAdapter::GetCdmContext()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return this;
}

Decryptor* CdmAdapter::GetDecryptor()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return this;
}

int CdmAdapter::GetCdmId() const
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return kInvalidCdmId;
}

void CdmAdapter::RegisterNewKeyCB(StreamType stream_type,
    const NewKeyCB& key_added_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    switch (stream_type) {
    case kAudio:
        new_audio_key_cb_ = key_added_cb;
        return;
    case kVideo:
        new_video_key_cb_ = key_added_cb;
        return;
    }

    NOTREACHED() << "Unexpected StreamType " << stream_type;
}

void CdmAdapter::Decrypt(StreamType stream_type,
    const scoped_refptr<DecoderBuffer>& encrypted,
    const DecryptCB& decrypt_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    cdm::InputBuffer input_buffer;
    std::vector<cdm::SubsampleEntry> subsamples;
    std::unique_ptr<DecryptedBlockImpl> decrypted_block(new DecryptedBlockImpl());

    ToCdmInputBuffer(encrypted, &subsamples, &input_buffer);
    cdm::Status status = cdm_->Decrypt(input_buffer, decrypted_block.get());

    if (status != cdm::kSuccess) {
        DVLOG(1) << __func__ << " failed with cdm::Error " << status;
        decrypt_cb.Run(ToMediaDecryptorStatus(status), nullptr);
        return;
    }

    scoped_refptr<DecoderBuffer> decrypted_buffer(
        DecoderBuffer::CopyFrom(decrypted_block->DecryptedBuffer()->Data(),
            decrypted_block->DecryptedBuffer()->Size()));
    decrypted_buffer->set_timestamp(
        base::TimeDelta::FromMicroseconds(decrypted_block->Timestamp()));
    decrypt_cb.Run(Decryptor::kSuccess, decrypted_buffer);
}

void CdmAdapter::CancelDecrypt(StreamType stream_type)
{
    // As the Decrypt methods are synchronous, nothing can be done here.
    DCHECK(task_runner_->BelongsToCurrentThread());
}

void CdmAdapter::InitializeAudioDecoder(const AudioDecoderConfig& config,
    const DecoderInitCB& init_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(audio_init_cb_.is_null());

    cdm::AudioDecoderConfig cdm_decoder_config;
    cdm_decoder_config.codec = ToCdmAudioCodec(config.codec());
    cdm_decoder_config.channel_count = ChannelLayoutToChannelCount(config.channel_layout());
    cdm_decoder_config.bits_per_channel = config.bits_per_channel();
    cdm_decoder_config.samples_per_second = config.samples_per_second();
    cdm_decoder_config.extra_data = const_cast<uint8_t*>(config.extra_data().data());
    cdm_decoder_config.extra_data_size = config.extra_data().size();

    cdm::Status status = cdm_->InitializeAudioDecoder(cdm_decoder_config);
    if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
        // DCHECK(status == cdm::kSessionError); http://crbug.com/570486
        DVLOG(1) << __func__ << " failed with cdm::Error " << status;
        init_cb.Run(false);
        return;
    }

    audio_samples_per_second_ = config.samples_per_second();
    audio_channel_layout_ = config.channel_layout();

    if (status == cdm::kDeferredInitialization) {
        DVLOG(1) << "Deferred initialization in " << __func__;
        audio_init_cb_ = init_cb;
        return;
    }

    init_cb.Run(true);
}

void CdmAdapter::InitializeVideoDecoder(const VideoDecoderConfig& config,
    const DecoderInitCB& init_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(video_init_cb_.is_null());

    cdm::VideoDecoderConfig cdm_decoder_config;
    cdm_decoder_config.codec = ToCdmVideoCodec(config.codec());
    cdm_decoder_config.profile = ToCdmVideoCodecProfile(config.profile());
    cdm_decoder_config.format = ToCdmVideoFormat(config.format());
    cdm_decoder_config.coded_size.width = config.coded_size().width();
    cdm_decoder_config.coded_size.height = config.coded_size().height();
    cdm_decoder_config.extra_data = const_cast<uint8_t*>(config.extra_data().data());
    cdm_decoder_config.extra_data_size = config.extra_data().size();

    cdm::Status status = cdm_->InitializeVideoDecoder(cdm_decoder_config);
    if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
        // DCHECK(status == cdm::kSessionError); http://crbug.com/570486
        DVLOG(1) << __func__ << " failed with cdm::Error " << status;
        init_cb.Run(false);
        return;
    }

    natural_size_ = config.natural_size();

    if (status == cdm::kDeferredInitialization) {
        DVLOG(1) << "Deferred initialization in " << __func__;
        video_init_cb_ = init_cb;
        return;
    }

    init_cb.Run(true);
}

void CdmAdapter::DecryptAndDecodeAudio(
    const scoped_refptr<DecoderBuffer>& encrypted,
    const AudioDecodeCB& audio_decode_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    cdm::InputBuffer input_buffer;
    std::vector<cdm::SubsampleEntry> subsamples;
    std::unique_ptr<AudioFramesImpl> audio_frames(new AudioFramesImpl());

    ToCdmInputBuffer(encrypted, &subsamples, &input_buffer);
    cdm::Status status = cdm_->DecryptAndDecodeSamples(input_buffer, audio_frames.get());

    const Decryptor::AudioFrames empty_frames;
    if (status != cdm::kSuccess) {
        DVLOG(1) << __func__ << " failed with cdm::Error " << status;
        audio_decode_cb.Run(ToMediaDecryptorStatus(status), empty_frames);
        return;
    }

    Decryptor::AudioFrames audio_frame_list;
    DCHECK(audio_frames->FrameBuffer());
    if (!AudioFramesDataToAudioFrames(std::move(audio_frames),
            &audio_frame_list)) {
        DVLOG(1) << __func__ << " unable to convert Audio Frames";
        audio_decode_cb.Run(Decryptor::kError, empty_frames);
        return;
    }

    audio_decode_cb.Run(Decryptor::kSuccess, audio_frame_list);
}

void CdmAdapter::DecryptAndDecodeVideo(
    const scoped_refptr<DecoderBuffer>& encrypted,
    const VideoDecodeCB& video_decode_cb)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DVLOG(3) << __func__ << " encrypted: " << encrypted->AsHumanReadableString();

    cdm::InputBuffer input_buffer;
    std::vector<cdm::SubsampleEntry> subsamples;
    std::unique_ptr<VideoFrameImpl> video_frame = allocator_->CreateCdmVideoFrame();

    ToCdmInputBuffer(encrypted, &subsamples, &input_buffer);
    cdm::Status status = cdm_->DecryptAndDecodeFrame(input_buffer, video_frame.get());

    if (status != cdm::kSuccess) {
        DVLOG(1) << __func__ << " failed with cdm::Error " << status;
        video_decode_cb.Run(ToMediaDecryptorStatus(status), nullptr);
        return;
    }

    scoped_refptr<VideoFrame> decoded_frame = video_frame->TransformToVideoFrame(natural_size_);
    video_decode_cb.Run(Decryptor::kSuccess, decoded_frame);
}

void CdmAdapter::ResetDecoder(StreamType stream_type)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_->ResetDecoder(ToCdmStreamType(stream_type));
}

void CdmAdapter::DeinitializeDecoder(StreamType stream_type)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_->DeinitializeDecoder(ToCdmStreamType(stream_type));

    // Reset the saved values from initializing the decoder.
    switch (stream_type) {
    case Decryptor::kAudio:
        audio_samples_per_second_ = 0;
        audio_channel_layout_ = CHANNEL_LAYOUT_NONE;
        break;
    case Decryptor::kVideo:
        natural_size_ = gfx::Size();
        break;
    }
}

cdm::Buffer* CdmAdapter::Allocate(uint32_t capacity)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return allocator_->CreateCdmBuffer(capacity);
}

void CdmAdapter::SetTimer(int64_t delay_ms, void* context)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    task_runner_->PostDelayedTask(FROM_HERE,
        base::Bind(&CdmAdapter::TimerExpired,
            weak_factory_.GetWeakPtr(), context),
        base::TimeDelta::FromMilliseconds(delay_ms));
}

void CdmAdapter::TimerExpired(void* context)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_->TimerExpired(context);
}

cdm::Time CdmAdapter::GetCurrentWallTime()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return base::Time::Now().ToDoubleT();
}

void CdmAdapter::OnResolvePromise(uint32_t promise_id)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_promise_adapter_.ResolvePromise(promise_id);
}

void CdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id,
    const char* session_id,
    uint32_t session_id_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_promise_adapter_.ResolvePromise(promise_id,
        std::string(session_id, session_id_size));
}

void CdmAdapter::OnRejectPromise(uint32_t promise_id,
    cdm::Error error,
    uint32_t system_code,
    const char* error_message,
    uint32_t error_message_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    cdm_promise_adapter_.RejectPromise(
        promise_id, ToMediaExceptionType(error), system_code,
        std::string(error_message, error_message_size));
}

void CdmAdapter::OnSessionMessage(const char* session_id,
    uint32_t session_id_size,
    cdm::MessageType message_type,
    const char* message,
    uint32_t message_size,
    const char* legacy_destination_url,
    uint32_t legacy_destination_url_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    // |legacy_destination_url| is obsolete and will be removed as part of
    // https://crbug.com/570216.

    const uint8_t* message_ptr = reinterpret_cast<const uint8_t*>(message);
    session_message_cb_.Run(
        std::string(session_id, session_id_size),
        ToMediaMessageType(message_type),
        std::vector<uint8_t>(message_ptr, message_ptr + message_size));
}

void CdmAdapter::OnSessionKeysChange(const char* session_id,
    uint32_t session_id_size,
    bool has_additional_usable_key,
    const cdm::KeyInformation* keys_info,
    uint32_t keys_info_count)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    CdmKeysInfo keys;
    keys.reserve(keys_info_count);
    for (uint32_t i = 0; i < keys_info_count; ++i) {
        const auto& info = keys_info[i];
        keys.push_back(new CdmKeyInformation(
            info.key_id, info.key_id_size,
            ToCdmKeyInformationKeyStatus(info.status), info.system_code));
    }

    // TODO(jrummell): Handling resume playback should be done in the media
    // player, not in the Decryptors. http://crbug.com/413413.
    if (has_additional_usable_key) {
        if (!new_audio_key_cb_.is_null())
            new_audio_key_cb_.Run();
        if (!new_video_key_cb_.is_null())
            new_video_key_cb_.Run();
    }

    session_keys_change_cb_.Run(std::string(session_id, session_id_size),
        has_additional_usable_key, std::move(keys));
}

void CdmAdapter::OnExpirationChange(const char* session_id,
    uint32_t session_id_size,
    cdm::Time new_expiry_time)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    session_expiration_update_cb_.Run(std::string(session_id, session_id_size),
        base::Time::FromDoubleT(new_expiry_time));
}

void CdmAdapter::OnSessionClosed(const char* session_id,
    uint32_t session_id_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    session_closed_cb_.Run(std::string(session_id, session_id_size));
}

void CdmAdapter::OnLegacySessionError(const char* session_id,
    uint32_t session_id_size,
    cdm::Error error,
    uint32_t system_code,
    const char* error_message,
    uint32_t error_message_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    // Obsolete and will be removed as part of https://crbug.com/570216.
}

void CdmAdapter::SendPlatformChallenge(const char* service_id,
    uint32_t service_id_size,
    const char* challenge,
    uint32_t challenge_size)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // TODO(jrummell): If platform verification is available, use it.
    NOTIMPLEMENTED();
    cdm::PlatformChallengeResponse platform_challenge_response = {};
    cdm_->OnPlatformChallengeResponse(platform_challenge_response);
}

void CdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // TODO(jrummell): If output protection is available, use it.
    NOTIMPLEMENTED();
}

void CdmAdapter::QueryOutputProtectionStatus()
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // TODO(jrummell): If output protection is available, use it.
    NOTIMPLEMENTED();
    cdm_->OnQueryOutputProtectionStatus(cdm::kQueryFailed, 0, 0);
}

void CdmAdapter::OnDeferredInitializationDone(cdm::StreamType stream_type,
    cdm::Status decoder_status)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DVLOG_IF(1, decoder_status != cdm::kSuccess)
        << __func__ << " failed with cdm::Error " << decoder_status;

    switch (stream_type) {
    case cdm::kStreamTypeAudio:
        base::ResetAndReturn(&audio_init_cb_)
            .Run(decoder_status == cdm::kSuccess);
        return;
    case cdm::kStreamTypeVideo:
        base::ResetAndReturn(&video_init_cb_)
            .Run(decoder_status == cdm::kSuccess);
        return;
    }

    NOTREACHED() << "Unexpected cdm::StreamType " << stream_type;
}

cdm::FileIO* CdmAdapter::CreateFileIO(cdm::FileIOClient* client)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    std::unique_ptr<CdmFileIO> file_io = create_cdm_file_io_cb_.Run(client);

    // The CDM owns the returned object and must call FileIO::Close()
    // to release it.
    return file_io.release();
}

bool CdmAdapter::AudioFramesDataToAudioFrames(
    std::unique_ptr<AudioFramesImpl> audio_frames,
    Decryptor::AudioFrames* result_frames)
{
    const uint8_t* data = audio_frames->FrameBuffer()->Data();
    const size_t data_size = audio_frames->FrameBuffer()->Size();
    size_t bytes_left = data_size;
    const SampleFormat sample_format = ToMediaSampleFormat(audio_frames->Format());
    const int audio_channel_count = ChannelLayoutToChannelCount(audio_channel_layout_);
    const int audio_bytes_per_frame = SampleFormatToBytesPerChannel(sample_format) * audio_channel_count;
    if (audio_bytes_per_frame <= 0)
        return false;

    // Allocate space for the channel pointers given to AudioBuffer.
    std::vector<const uint8_t*> channel_ptrs(audio_channel_count, nullptr);
    do {
        // AudioFrames can contain multiple audio output buffers, which are
        // serialized into this format:
        // |<------------------- serialized audio buffer ------------------->|
        // | int64_t timestamp | int64_t length | length bytes of audio data |
        int64_t timestamp = 0;
        int64_t frame_size = -1;
        const size_t kHeaderSize = sizeof(timestamp) + sizeof(frame_size);
        if (bytes_left < kHeaderSize)
            return false;

        memcpy(&timestamp, data, sizeof(timestamp));
        memcpy(&frame_size, data + sizeof(timestamp), sizeof(frame_size));
        data += kHeaderSize;
        bytes_left -= kHeaderSize;

        // We should *not* have empty frames in the list.
        if (frame_size <= 0 || bytes_left < base::checked_cast<size_t>(frame_size)) {
            return false;
        }

        // Setup channel pointers.  AudioBuffer::CopyFrom() will only use the first
        // one in the case of interleaved data.
        const int size_per_channel = frame_size / audio_channel_count;
        for (int i = 0; i < audio_channel_count; ++i)
            channel_ptrs[i] = data + i * size_per_channel;

        const int frame_count = frame_size / audio_bytes_per_frame;
        scoped_refptr<media::AudioBuffer> frame = media::AudioBuffer::CopyFrom(
            sample_format, audio_channel_layout_, audio_channel_count,
            audio_samples_per_second_, frame_count, &channel_ptrs[0],
            base::TimeDelta::FromMicroseconds(timestamp));
        result_frames->push_back(frame);

        data += frame_size;
        bytes_left -= frame_size;
    } while (bytes_left > 0);

    return true;
}

} // namespace media
